﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Jayden.Dll.CodeGen
{
	public interface Generator
	{
		void Generate(CodeBuilder builder);
	}
	public class Variable
	{
		private static string MakeVariable(string name)
		{
			if (char.IsUpper(name[0]))
				name = char.ToLower(name[0]) + name.Substring(1);
			return name;
		}
		private static string MakeMember(string name)
		{
			if (char.IsLower(name[0]))
				name = char.ToUpper(name[0]) + name.Substring(1);
			return name;
		}

		private string m_Name = "Foo";
		private string m_Type = "object";

		public string Name { get { return m_Name; } set { m_Name = value; } }
		public string Type { get { return m_Type; } set { m_Type = value; } }

		public Variable(string name, string type = "object")
		{
			Name = name;
			Type = type;
		}
		public string VariableName { get { return MakeVariable(Name); } }
		public string MemberName { get { return MakeMember(Name); } }

		public override string ToString() { return string.Format("{0} {1}", Type, Name); }
	}
	public class AccessVariable : Variable
	{
		private string m_Access = "public";

		public string Access { get { return m_Access; } set { m_Access = value; } }

		public AccessVariable(string name, string type = "object", string access = "public")
			: base(name, type)
		{
			Access = access;
		}

		public override string ToString() { return string.Format("{0} {1}", Access, base.ToString()); }
	}
	public class Event : AccessVariable
	{
		public Event(string name, string type = "Foo", string access = "public")
			: base(name, type, access) { }

		public void Generate(CodeBuilder builder)
		{
			builder.AppendLine("{0} event {1} {2};", Access, Type, MemberName);
		}
	}
	public class Member : AccessVariable
	{
		private string m_DefaultValue;
		private bool m_Getter = true;
		private bool m_Setter = true;

		public string DefaultValue { get { return m_DefaultValue; } set { m_DefaultValue = value; } }
		public bool Getter { get { return m_Getter; } set { m_Getter = value; } }
		public bool Setter { get { return m_Setter; } set { m_Setter = value; } }


		public Member(string name, string type = "object", string access = "private")
			: base(name, type, access) { }

		public void GenerateMember(CodeBuilder builder)
		{
			if (string.IsNullOrWhiteSpace(DefaultValue))
				builder.AppendLine("{0} {1} m_{2};", Access, Type, MemberName);
			else
				builder.AppendLine("{0} {1} m_{2} = {3};", Access, Type, MemberName, DefaultValue);
		}
		public void GenerateProperty(CodeBuilder builder)
		{
			if (Getter && Setter)
				builder.AppendLine("{0} {1} {2} {{ get {{ throw new NotImplementedException(); }} set {{ throw new NotImplementedException(); }} }}", Access, Type, MemberName);
			else if (Setter)
				builder.AppendLine("{0} {1} {2} {{ set {{ throw new NotImplementedException(); }} }}", Access, Type, MemberName);
			else if (Getter)
				builder.AppendLine("{0} {1} {2} {{ get {{ throw new NotImplementedException(); }} }}", Access, Type, MemberName);
		}
		public void GeneratePropertyForInterface(CodeBuilder builder)
		{
			if (Getter && Setter)
				builder.AppendLine("{1} {2} {{ get; set; }}", Type, MemberName);
			else if (Setter)
				builder.AppendLine("{1} {2} {{ set; }}", Type, MemberName);
			else if (Getter)
				builder.AppendLine("{1} {2} {{ get; }}", Type, MemberName);
		}
		public void GeneratePublicPropertyFromMember(CodeBuilder builder)
		{
			if (Getter && Setter)
				builder.AppendLine("{0} {1} {2} {{ get {{ return m_{2}; }} set {{ m_{2} = value; }} }}", "public", Type, MemberName);
			else if (Setter)
				builder.AppendLine("{0} {1} {2} {{ set {{ m_{2} = value; }} }}", "public", Type, MemberName);
			else if (Getter)
				builder.AppendLine("{0} {1} {2} {{ get {{ return m_{2}; }} }}", "public", Type, MemberName);
		}

		public override string ToString() { return string.Format("{0} {1} m_{2}", Access, Type, MemberName); }
	}
	public class Method : AccessVariable
	{
		private bool m_Override;
		private bool m_New;
		private bool m_Abstract;

		public bool Override { get { return m_Override; } set { m_Override = value; } }
		public bool New { get { return m_New; } set { m_New = value; } }
		public bool Abstract { get { return m_Abstract; } set { m_Abstract = value; } }

		public List<Member> Arguments = new List<Member>();

		public Method(string name, string type = "void", string access = "public")
			: base(name, type, access) { }

		public void Generate(CodeBuilder builder)
		{
			StringBuilder arguments = new StringBuilder();
			foreach (Member arg in Arguments)
			{
				if (arguments.Length > 0)
					arguments.Append(", ");
				if (string.IsNullOrWhiteSpace(arg.DefaultValue))
					arguments.AppendFormat("{0} {1}", arg.Type, arg.VariableName);
				else
					arguments.AppendFormat("{0} {1} = {2}", arg.Type, arg.VariableName, arg.DefaultValue);
			}
			StringBuilder access = new StringBuilder();
			access.Append(Access);
			if (New)
				access.Append(" new");
			if (Override)
				access.Append(" override");
			if (Abstract)
				access.Append(" abstract");
			builder.AppendLine("{0} {1} {2}({3}) {{ throw new NotImplementedException(); }}", access.ToString(), Type, MemberName, arguments.ToString());
		}
		public void GenerateForInterface(CodeBuilder builder)
		{
			StringBuilder arguments = new StringBuilder();
			foreach (Member arg in Arguments)
			{
				if (arguments.Length > 0)
					arguments.Append(", ");
				if (string.IsNullOrWhiteSpace(arg.DefaultValue))
					arguments.AppendFormat("{0} {1}", arg.Type, arg.VariableName);
				else
					arguments.AppendFormat("{0} {1} = {2}", arg.Type, arg.VariableName, arg.DefaultValue);
			}
			builder.AppendLine("{1} {2}({3});", Type, MemberName, arguments.ToString());

		}
		public void GenerateDelegate(CodeBuilder builder)
		{
			StringBuilder arguments = new StringBuilder();
			foreach (Member arg in Arguments)
			{
				if (arguments.Length > 0)
					arguments.Append(", ");
				arguments.AppendFormat("{0} {1}", arg.Type, arg.VariableName);
			}
			builder.AppendLine("{0} delegate {1} {2}({3});", Access, Type, MemberName, arguments.ToString());
		}
	}
	public class Entity : Generator
	{
		private string m_Access = "public";
		private string m_EntityType = "class";
		private string m_Name;
		private string m_BaseName;
		private List<string> m_Interfaces = new List<string>();
		private List<Entity> m_InnerClasses = new List<Entity>();
		private Dictionary<string, Member> m_Members = new Dictionary<string, Member>();
		private Dictionary<string, Member> m_Properties = new Dictionary<string, Member>();
		private Dictionary<string, Method> m_Delegates = new Dictionary<string, Method>();
		private Dictionary<string, Event> m_Events = new Dictionary<string, Event>();
		private List<Method> m_Methods = new List<Method>();

		public Entity(string name)
		{
			m_Name = name;
		}

		public bool AddMember(Member member)
		{
			if (!CanCreateMember(member.Name))
				return false;
			m_Members.Add(member.Name, member);
			return true;
		}

		public bool AddMethod(Method method)
		{
			string[] types = new string[method.Arguments.Count];
			for(int c=0;c<types.Length;c++)
				types[c] = method.Arguments[c].Type;
			if (!CanCreateMethod(method.Name, types))
				return false;
			m_Methods.Add(method);
			return true;
		}

		public void Generate(CodeBuilder builder)
		{
			StringBuilder inherits = new StringBuilder();
			if (!string.IsNullOrWhiteSpace(m_BaseName))
			{
				inherits.Append(" : ");
				inherits.Append(m_BaseName);
			}
			if (m_Interfaces.Count > 0)
			{
				if (inherits.Length == 0)
					inherits.Append(" : ");
				else
					inherits.Append(", ");
				for (int c = 0; c < m_Interfaces.Count; c++)
				{
					if(c != 0)
						inherits.Append(", ");
					inherits.Append(m_Interfaces[c]);
				}
			}
			builder.AppendLine("{0} {1} {2}{3}", m_Access, m_EntityType, m_Name, inherits.ToString());
			builder.OpenParenthesis();

			foreach (Entity innerClass in m_InnerClasses)
				innerClass.Generate(builder);

			foreach (KeyValuePair<string, Member> pair in m_Members)
				pair.Value.GenerateMember(builder);

			foreach (KeyValuePair<string, Method> pair in m_Delegates)
				pair.Value.GenerateDelegate(builder);

			foreach (KeyValuePair<string, Event> pair in m_Events)
				pair.Value.Generate(builder);

			GenerateCtor(builder);
			GenerateCtor(builder, m_Members.Values.ToArray<Member>());
			GenerateCtorByClone(builder);

			foreach (KeyValuePair<string, Member> pair in m_Members)
				pair.Value.GeneratePublicPropertyFromMember(builder);

			foreach (KeyValuePair<string, Member> pair in m_Properties)
				pair.Value.GenerateProperty(builder);

			foreach (Method method in m_Methods)
				method.Generate(builder);

			GenerateGetHashCode(builder);
			GenerateEquals(builder);
			GenerateToString(builder);

			builder.CloseParenthesis();
		}

		private void GenerateCtor(CodeBuilder builder, params Member[] variables)
		{
			StringBuilder arguments = new StringBuilder();
			foreach (Member arg in variables)
			{
				if (arguments.Length > 0)
					arguments.Append(", ");
				if (string.IsNullOrWhiteSpace(arg.DefaultValue))
					arguments.AppendFormat("{0} {1}", arg.Type, arg.VariableName);
				else
					arguments.AppendFormat("{0} {1} = {2}", arg.Type, arg.VariableName, arg.DefaultValue);
			}

			builder.AppendLine("{0} {1}({2})", "public", m_Name, arguments.ToString());
			builder.OpenParenthesis();
			foreach (Member m in variables)
				builder.AppendLine("m_{0} = {1};", m.MemberName, m.VariableName);
			builder.CloseParenthesis();
		}
		private void GenerateCtorByClone(CodeBuilder builder)
		{
			builder.AppendLine("{0} {1}({1} source)", "public", m_Name);
			builder.OpenParenthesis();
			foreach (Member m in m_Members.Values)
				builder.AppendLine("m_{0} = source.m_{0};", m.MemberName);
			builder.CloseParenthesis();
		}

		private void GenerateToString(CodeBuilder builder)
		{
			builder.AppendLine("public override string ToString() {{ throw new NotImplementedException(); }}");
		}

		private void GenerateEquals(CodeBuilder builder)
		{
			builder.AppendLine("public override bool Equals(object obj) {{ throw new NotImplementedException(); }}");
		}

		private void GenerateGetHashCode(CodeBuilder builder)
		{
			builder.AppendLine("public override int GetHashCode() {{ throw new NotImplementedException(); }}");
		}

		public bool CanCreateMember(string name)
		{
			Member member;
			if (m_Members.TryGetValue(name, out member))
				return false;
			if (m_Properties.TryGetValue(name, out member) && (member.Getter || member.Setter))
				return false;
			if (m_Properties.TryGetValue("m_" + name, out member))
				return false;
			foreach (Method method in m_Methods)
				if (method.Name == ("m_" + name))
					return false;
			return true;
		}
		public bool CanCreateProperty(string name)
		{
			Member member;
			if (m_Members.TryGetValue(name, out member) && (member.Getter || member.Setter))
					return false;
			if (m_Properties.TryGetValue(name, out member) && (member.Getter || member.Setter))
				return false;
			foreach(Method method in m_Methods)
				if(method.Name == name)
					return false;
			return true;
		}
		public bool CanCreateMethod(string name, params string[] argTypes)
		{
			Member member;
			if (m_Members.TryGetValue(name, out member) && (member.Getter || member.Setter))
				return false;
			if (m_Properties.TryGetValue(name, out member) && (member.Getter || member.Setter))
				return false;
			foreach (Method method in m_Methods)
				if (method.Name == name)
				{
					if (method.Arguments.Count == argTypes.Length)
					{
						bool same = true;
						for (int c = 0; same && c < argTypes.Length; c++)
							same = (method.Arguments[c].Type == argTypes[c]);
						if(same)
							return false;
					}
				}
			return true;
		}
		public bool CanAddInnerClass(Entity entity, int level = 0)
		{
			if (entity == this)
				return false;
			foreach (Entity innerClass in m_InnerClasses)
				if (!innerClass.CanAddInnerClass(this, level + 1))
					return false;
				else if (level == 0 && innerClass.m_Name == entity.m_Name)
					return false;
			return true;
		}
	}
	public class Namespace : Generator
	{
		private string m_Name;
		private List<Entity> m_Entities = new List<Entity>();

		public Namespace(string name)
		{
			m_Name = name;
		}

		public void AddEntity(Entity entity) { m_Entities.Add(entity); }

		public void Generate(CodeBuilder builder)
		{
			builder.AppendLine("namespace {0}", m_Name);
			builder.OpenParenthesis();
			foreach (Entity entity in m_Entities)
				entity.Generate(builder);
			builder.CloseParenthesis();
		}
	}
	public class File : Generator
	{
		private string m_Name;
		private List<string> m_Usings = new List<string>();
		private List<Namespace> m_Namespaces = new List<Namespace>();

		public File(string name)
		{
			m_Name = name;
		}

		public void AddNamespace(Namespace ns) { m_Namespaces.Add(ns); }
		public void AddUsing(string name) { m_Usings.Add(name); }

		public void Generate(CodeBuilder builder)
		{
			foreach (string name in m_Usings)
				builder.AppendLine("using {0};", name);
			builder.AppendLine();
			foreach (Namespace ns in m_Namespaces)
				ns.Generate(builder);
		}

		public void Save(string directory)
		{
			string filepath = Path.Combine(directory, m_Name);
			using (CodeBuilder builder = new CodeBuilder())
			{
				Generate(builder);
				System.IO.File.WriteAllText(filepath, builder.ToString());
			}
		}
	}
}
